module('tcpnet',package.seeall) 
require 'waitingnet' 
--协议包头标志  
local MSG_BEGINFLAG = nil;
--协议包头大小
local PACKET_HEADER_SIZE = 15;
--心跳时长
local HEART_DURING = 5000 
local m_sock = nil;
--分段接收
local m_perrecive = 1000; 
local m_lasttime=os.time(); 
--网络运行的定时器
local m_scheduleRun = nil;
--网络连接的定时器
local m_scheduleCheckConnecting = nil;
--用来存放发送包的缓存
local m_sendBuf = '';    
local m_packetSize = 0;
local m_cursize = 0;
local m_protocolnumber = 0;
local m_serialNumber = 0;
local m_starttime = 0;
local m_recvBuf = '';    
local m_headbuffer = nil;
local m_errorcode = 0; 
local m_msgarr = {};
local m_connetPad = nil;
local m_errorPad=nil;
local m_loginProtocId = nil;--登录协议号，会根据不同渠道或正版方式不一样
--0表示未登录
--1表示已登录
local m_loginstate=0;
 
--心跳当前保存时间戳
local m_curHeartTime = 0
--保存正常登陆时的回调
local m_loginCallback = nil
--重新连接
function reconnect() 
  conectToServer();
end
--从后台回来的时候重连
function connectFromBack()
  local curtime=os.time();
  if curtime-m_lasttime >= 300 then 
    m_lasttime=curtime;
    reconnect();
  end
end  
--显示连接中的动画
local function showConnectingMovie() 
  if not m_connetPad then 
     m_connetPad=WaitingDialog:showWaiting('正在连接服务器...');
    local function eventfun(event) 
        if event == "cleanup" then  
			m_connetPad=nil; 
        end
    end   
	regNodeEvent(m_connetPad,eventfun)  
  end
end
--移除连接中的动画
local function removeConnectingMovie()
  if m_connetPad then 
	m_connetPad:removeFromParent();
	m_connetPad=nil;
	print("移除连接菊")
  end
end
--停止网络运行
local function endRun()
  if m_scheduleRun ~= nil then
    cc.Director:getInstance():getScheduler():unscheduleScriptEntry(m_scheduleRun);
    m_scheduleRun = nil;
  end
end
--关闭套接字
local function closeSock() 
  if(m_sock)then
    c_close();
    m_sock = nil;
  end
end
--断开连接
function disConnectNet()
  endRun();
  closeSock();
end 
function clearnet()  
  m_protocolnumber = 0;
  m_packetSize = 0;
  m_cursize = 0;
  m_recvBuf = ''; 
  m_headbuffer=nil;
  m_curHeartTime = 0
  disConnectNet();
  c_resetDataKey();--重置datakey
  m_msgarr={};
  if m_connetPad then 
	m_connetPad:removeFromParent();
	m_connetPad=nil;
  end 
  print('调用了clearnet') 
  waitingnet.clear();
end
--显示网络错误
function showNetError(err)
  err = err or '';
  userop.userop('网络错误=' .. err);
  clearnet();  
  local funcOk = function()
    reconnect();     
  end
  local funcExit = function()
	--如果是渠道就去注销
    if gd_channel ~= '' then 
		channel.qdLogOff();
	else
		SceneManager:ins():backToLogin();
	end
  end  
  
  if m_errorPad then 
    return; 
  end
  
  m_errorPad=MessageDialog:show("网络断开，请重新连接",funcOk,funcExit,true,true) 
  local function eventfun(event) 
	if event == "cleanup" or event == 'exit' then 
		m_errorPad=nil; 
	end
  end      
  regNodeEvent(m_errorPad,eventfun)  
end
 
function SendPacket(serialNumber,msg,packetid)  
  --如果socket还未创建就显示失败
  if not m_sock then  
	print("SendPacket(serialNumber,msg) error");
    showNetError();
    return;
  end 
  --将要发送的协议放到队列里面，只有收到了才能移除  
  waitingnet.waitingNetBegin(serialNumber,packetid); 
  m_sendBuf = m_sendBuf .. msg; 
end

function SendHeartPacket(serialNumber,msg)  
  --如果socket还未创建就显示失败
  if not m_sock then  
	print("SendPacket(serialNumber,msg) error");
    showNetError();
    return;
  end 
  --发送心跳包不弹出菊花
  m_sendBuf = m_sendBuf .. msg; 
end


--检查网络错误
local function checkerror(err)
  if err == 'closed' then   
    userop.userop('网络关闭了') 
    showNetError();
  end 
end
function resetLoginState()
	m_loginstate = 0;
end
local m_logindata=nil;
--保存登录信息
function setLoginData(loginData,PROTOCID)
	m_logindata=loginData;	
	m_loginProtocId = PROTOCID;
end
 
function setLoginCallback(callback)
    m_loginCallback=callback;
end

local function reLogin()
	local function onLogin()
		if m_loginstate == 1 then  
			Comm:ins():sendMessageList();
		end
	end 
	--如果是渠道重新登录需要发送playerid号
	if m_logindata["player_id"] then
		m_logindata["player_id"] = PlayerData.player_login_data.player_id
		print('m_logindata["player_id"]=',m_logindata["player_id"])
	end  
	Comm:ins():sendMessage(m_loginProtocId,m_logindata,onLogin);
end
--正常登陆
local function normalLogin()
	Comm:ins():sendMessage(m_loginProtocId,m_logindata,m_loginCallback);
end
--发送心跳
local function sendHeartPackage()
    Comm:ins():sendHeartPackage()
    m_curHeartTime = TuiManager:mtime()
end

--心跳定时器
local function heartOnFrame()
    if m_curHeartTime ~= 0 and TuiManager:mtime() - m_curHeartTime >= HEART_DURING then
        print("heartOnFrame~~~~~~~~~~~~~~~~~")
        sendHeartPackage()
    end
end
--处理消息接收
local function ProcessInput() 
  if(not m_headbuffer)then
    --如果还未收取消息则初始化消息并尝试接收服务器消息
    m_packetSize=0;
    m_protocolnumber=0;
	m_serialNumber=0;
	--先接受包头
    m_headbuffer,err = c_receive(PACKET_HEADER_SIZE);  
    --判断网络是否出错 
    checkerror(err); 
    if m_headbuffer then
      --记住上一次服务器有响应的时间
      m_lasttime=os.time();
      --如果服务器有消息
	  --包头格式
	  --包体长，接口，包序号，时间戳，数据类型，压缩类型，结果代码，包数据，校验码 
	  m_packetSize,m_protocolnumber,m_serialNumber,_shijiancuo,_shujuleixing,
	  _yasuoleixing,m_errorcode = c_dealHead(m_headbuffer)--这个函数需要在C++中实现  
	  m_packetSize = m_packetSize + 4;
      m_recvBuf = ''; 
	  print("需要接受的包大小是" .. m_packetSize)
    end
  else
    local protobuf = nil;
    local err = nil;
    if(m_packetSize > 0) then
      --如果消息包包体未接收则分段接收
      m_cursize = m_perrecive;
      if m_packetSize < m_perrecive then
        m_cursize = m_packetSize;
      end  
      protobuf,err = c_receive(m_cursize);  
	  print('xxxxxxxxxxxxxxxxxxxxxxxxxxx接收的大小xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=' .. m_cursize)
      checkerror(err);
       if not protobuf then 
		  --如果没有消息包包体则返回
          return;
      end  
      --接入到接收包的末尾
      m_recvBuf = m_recvBuf .. protobuf;
      --计算剩余的包大小
      m_packetSize = m_packetSize - m_cursize;
      --如果还有剩余包则返回继续接收
      if m_packetSize > 0 then
        return;
      end
    end 
    --表示从网卡中提取一个完整切只有一个完整的包
    waitingnet.waitingNetEnd(m_serialNumber); 
    local pack=string.sub(m_recvBuf,1,-5);
	local code=string.sub(m_recvBuf,-4); --校验码不知道干啥，怕粘包 
	local jsonbuffer=c_convertBufferToJason(m_headbuffer .. pack,m_protocolnumber);--这个需要在C++中实现字节流转换成jason包体
	local rmsg=json.decode(jsonbuffer);
    --print("协议数据",do_serialize(rmsg)) 
    if Comm:ins():isLoginProtoc(m_protocolnumber) then
		if not rmsg.player_login_data then
			print("没有给玩家信息了")
			showNetError();
			return;
		end
		c_setDataKey(rmsg.player_login_data.datakey);
		m_loginstate = 1;
		--重新绑定渠道登录，一定要在登录状态切换之后
		channel.bindQdLogOff(true);
        sendHeartPackage();  
    end
	rmsg.serialNumber = m_serialNumber;
	rmsg.protocolnumber = m_protocolnumber;
	rmsg.errorCode = rmsg.errorCode or m_errorcode;
	--这边已经拼装好一个完整的包了，就直接扔给Comm处理
    table.insert(m_msgarr,rmsg);
    m_protocolnumber=0;
	m_serialNumber=0;
    m_packetSize = 0;
    m_cursize = 0;
    m_recvBuf = ''; 
	m_headbuffer=nil;
  end
end
--统一发送数据的地方
local function ProcessOutput()
  if(#m_sendBuf>0)then
    --如果有数据则发送
	print ('发送的协议长度为',#m_sendBuf)
    local _len ,_error= c_send(m_sendBuf); 
    if _len ~= nil and _len > 0 then
      m_sendBuf=string.sub(m_sendBuf,_len+1); 
    end
  end
end
function pullMessage()
  local msg=m_msgarr[1];
  if not msg then 
	return nil;
  end
  table.remove(m_msgarr,1);
  return msg;
end

local function startRun() 
  local function run()
    ProcessInput();
    ProcessOutput();
	Comm:ins():onFrame();
    heartOnFrame()
  end
  if m_scheduleRun == nil then
    m_scheduleRun = cc.Director:getInstance():getScheduler():scheduleScriptFunc(run,0, false);
  end
end

local function startIdentify() 
  local function identify() 
      --先接受包头 
      local buffer,err = c_receive(4); 
	  checkerror(err);
	  if buffer then  
		local _len ,_error= c_send('gate');
		checkerror(err);
		if _len == 4 then 
			--表示认证成功并且停止连接菊花
			userop.userop('认证成功');
			removeConnectingMovie();
			if m_loginstate==1 then 
				--表示已经在登录状态了，玩家不会主动去除法登录了，需要这边自动去完成登录
				reLogin();
            else
                --表示 正常登陆
                normalLogin()
			end
			--启动数据发收定时器 
			endRun();
			startRun();
		else
			showNetError();
		end
	  end
  end
  local str='GET /gate HTTP/1.1\r\nHost: ' .. HOST_IP
	.. ':' .. HOST_PORT .. '\r\nContent-Length: 0\r\n\r\n'; 
  print('认证len= ',#str)
  local _len ,_error= c_send(str);
  if _len ~= nil then 
	if m_scheduleRun == nil then
		m_scheduleRun = cc.Director:getInstance():getScheduler():scheduleScriptFunc(identify,0, false);
	end
  else 
	showNetError(_error);
  end  
end

local function connectSucced() 
  return c_connectSucced();
end 
local function endCheckConnecting()
  if m_scheduleCheckConnecting ~= nil then
    cc.Director:getInstance():getScheduler():unscheduleScriptEntry(m_scheduleCheckConnecting);
    m_scheduleCheckConnecting = nil;
  end
end 
--开始连接网络
local function startCheckConnecting()
  m_starttime = os.time();
  local function netIsConnecting() 
    local curtime = os.time();
    if curtime - m_starttime > 20 then
	  --如果时间太长了就不管网络情况了，直接停止连接和弹出错误提示
      endCheckConnecting();
	  print("endCheckConnecting error");
      showNetError();  
      return;
    end
    if (connectSucced()) then
	  --连接成功后就停止连接的菊花
      userop.userop('连接服务器成功,开始认证');
	  m_connetPad:setMessage('开始认证...');
	  --并且停止检查连接情况了，因为已经连接上了
      endCheckConnecting(); 
	  --开始认证
	  startIdentify(); 
    end   
  end
  if m_scheduleCheckConnecting == nil then
    m_scheduleCheckConnecting = cc.Director:getInstance():getScheduler():scheduleScriptFunc(netIsConnecting,0, false);
  end
end

local function connectSock()
  m_sock = c_connect(HOST_IP,HOST_PORT); 
end

local function sock()
  --不管三七二十八先断开连接
  userop.userop('连接服务器');
  disConnectNet();
  --重新创建socket
  connectSock();
  --开始检查连接情况
  startCheckConnecting(); 
  print("开始连接菊",HOST_IP,HOST_PORT)
  showConnectingMovie();
end

function conectToServer()
  --停止数据接收和发送
  endRun();   
  sock();
end
 
function backToLogin() 
  --退回到登录界面 
  
end

local g_serialNumber=0;
function newSerialNumber()
  g_serialNumber=g_serialNumber+1;
  return g_serialNumber;
end
